// Required boost stuff
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
#include <MyPointers.h>

// Classes to wrap
#include <Hello.h>
#include <Derived.h>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>

using namespace boost::python;

#include <boost/python/pointee.hpp>
#include <boost/python/class.hpp>

namespace boost { namespace python
{
  template <class T> struct pointee<my_ptr<T> >
  {
     typedef T type;
  };

  template<class T> inline T* get_pointer(my_ptr<T> const & p)
  {
      return p.get();
  }

  template <class T> struct pointee<ref_ptr<T> >
  {
     typedef T type;
  };

  template<class T> inline T* get_pointer(ref_ptr<T> const & p)
  {
      return p.get();
  }

}}


namespace
{

struct Base_Wrapper: Base
{
    Base_Wrapper(PyObject* py_self_, const Base& p0):
        Base(p0), py_self(py_self_) {}

    Base_Wrapper(PyObject* py_self_):
        Base(), py_self(py_self_) {}

    void printName() {
        call_method< void >(py_self, "printName");
    }

    void default_printName() {
        Base::printName();
    }

    PyObject* py_self;
};

struct Child1_Wrapper: Child1
{
    Child1_Wrapper(PyObject* py_self_, const Child1& p0):
        Child1(p0), py_self(py_self_) {}

    Child1_Wrapper(PyObject* py_self_):
        Child1(), py_self(py_self_) {}

    void printName() {
        call_method< void >(py_self, "printName");
    }

    void default_printName() {
        Child1::printName();
    }

    PyObject* py_self;
};

struct Child2_Wrapper: Child2
{
    Child2_Wrapper(PyObject* py_self_, const Child2& p0):
        Child2(p0), py_self(py_self_) {}

    Child2_Wrapper(PyObject* py_self_):
        Child2(), py_self(py_self_) {}

    void printName() {
        call_method< void >(py_self, "printName");
    }

    void default_printName() {
        Child2::printName();
    }

    PyObject* py_self;
};

struct GrandChild1_Wrapper: GrandChild1
{
    GrandChild1_Wrapper(PyObject* py_self_, const GrandChild1& p0):
        GrandChild1(p0), py_self(py_self_) {}

    GrandChild1_Wrapper(PyObject* py_self_):
        GrandChild1(), py_self(py_self_) {}

    void printName() {
        call_method< void >(py_self, "printName");
    }

    void default_printName() {
        GrandChild1::printName();
    }

    PyObject* py_self;
};

//BOOST_PYTHON_MODULE(hello)

void init_module_hello();

extern "C" void inithello()
{ boost::python::detail::init_module("hello", &init_module_hello); }

void init_module_hello()
{

   class_<Hello, my_ptr<Hello> > hello_class("Hello");

   hello_class.def("setMessage", &Hello::setMessage);
   hello_class.def("printWelcome", &Hello::printWelcome);

   def("newSharedHello", &newSharedHello);
   def("newPtrHello", &newPtrHello, return_value_policy<manage_new_object>());
   def("helloRef", &helloRef, return_internal_reference<>());

   /*
   class_<Greeter, my_ptr<Greeter> >("Greeter")
      .def("greet", &Greeter::greet)
      .def("setHello", &Greeter::setHello)
      .def("myHello", &Greeter::myHello)
   ;

   class_< Base, my_ptr<Base_Wrapper> >("Base", init<  >())
        .def(init< const Base& >())
        .def("printName", &Base::printName, &Base_Wrapper::default_printName)
        .def("printLocal", &Base::printLocal)
    ;

    class_< Child1, bases< Base > , my_ptr<Child1_Wrapper> >("Child1", init<  >())
        .def(init< const Child1& >())
        .def("printName", (void (Child1::*)() )&Child1::printName, (void (Child1_Wrapper::*)())&Child1_Wrapper::default_printName)
        .def("printLocal", &Child1::printLocal)
    ;

    class_< Child2, bases< Base > , my_ptr<Child2_Wrapper> >("Child2", init<  >())
        .def(init< const Child2& >())
        .def("printName", (void (Child2::*)() )&Child2::printName, (void (Child2_Wrapper::*)())&Child2_Wrapper::default_printName)
        .def("printLocal", &Child2::printLocal)
    ;

    class_< GrandChild1, bases< Child1 > , my_ptr<GrandChild1_Wrapper> >("GrandChild1", init<  >())
        .def(init< const GrandChild1& >())
        .def("printName", (void (GrandChild1::*)() )&GrandChild1::printName, (void (GrandChild1_Wrapper::*)())&GrandChild1_Wrapper::default_printName)
        .def("printLocal", &GrandChild1::printLocal)
    ;
    */

   class_<Greeter, my_ptr<Greeter> >("Greeter")
      .def("greet", &Greeter::greet)
      .def("setHello", &Greeter::setHello)
      .def("myHello", &Greeter::myHello)
   ;

   class_< Base, ref_ptr<Base> >("Base", init<  >())
        .def(init< const Base& >())
        .def("printName", &Base::printName)
        .def("printLocal", &Base::printLocal)
    ;

    class_< Child1, bases< Base > , ref_ptr<Child1> >("Child1", init<  >())
        .def(init< const Child1& >())
        .def("printName", &Child1::printName)
        .def("printLocal", &Child1::printLocal)
    ;

    class_< Child2, bases< Base > , ref_ptr<Child2> >("Child2", init<  >())
        .def(init< const Child2& >())
        .def("printName", &Child2::printName)
        .def("printLocal", &Child2::printLocal)
    ;

    class_< GrandChild1, bases< Child1 > , ref_ptr<GrandChild1> >("GrandChild1", init<  >())
        .def(init< const GrandChild1& >())
        .def("printName", &GrandChild1::printName)
        .def("printLocal", &GrandChild1::printLocal)
    ;

    // Can I make ptr type implicitly convertible to ref_type
    /*
    implicitly_convertible<my_ptr<Base_Wrapper>, my_ptr<Base> >();
    implicitly_convertible<my_ptr<Child1_Wrapper>, my_ptr<Child1> >();
    implicitly_convertible<my_ptr<Child2_Wrapper>, my_ptr<Child2> >();
    implicitly_convertible<my_ptr<GrandChild1_Wrapper>, my_ptr<GrandChild1> >();
    */

    /*
    implicitly_convertible<my_ptr<Child1>, my_ptr<Base> >();
    implicitly_convertible<my_ptr<Child2>, my_ptr<Base> >();
    implicitly_convertible<my_ptr<GrandChild1>, my_ptr<Base> >();
    implicitly_convertible<my_ptr<GrandChild1>, my_ptr<Child1> >();
    */

    /*
    implicitly_convertible<my_ptr<Base>, ref_ptr<Base> >();
    implicitly_convertible<my_ptr<Child1>, ref_ptr<Child1> >();
    implicitly_convertible<my_ptr<Child2>, ref_ptr<Child2> >();
    implicitly_convertible<my_ptr<GrandChild1>, ref_ptr<GrandChild1> >();
    */

    implicitly_convertible<ref_ptr<Base>, my_ptr<Base> >();
    implicitly_convertible<ref_ptr<Child1>, my_ptr<Child1> >();
    implicitly_convertible<ref_ptr<Child2>, my_ptr<Child2> >();
    implicitly_convertible<ref_ptr<GrandChild1>, my_ptr<GrandChild1> >();
    implicitly_convertible<ref_ptr<GrandChild1>, my_ptr<Base> >();

    register_ptr_to_python<my_ptr<Base> >();
    register_ptr_to_python<my_ptr<Child1> >();
    register_ptr_to_python<my_ptr<Child2> >();
    register_ptr_to_python<my_ptr<GrandChild1> >();

    class_<std::vector<my_ptr<Base> > >("vector_Base_")
      .def(vector_indexing_suite<std::vector<my_ptr<Base> >, true >());

    def("createRandomPtr", &createRandomPtr);
    def("examineDerived", &examineDerived);
    def("createRandomList", &createRandomList);

    def("ToRefPtr", &ToRefPtr<Base>);
    def("ToMyPtr", &ToMyPtr<Base>);
    def("ToRefPtr", &ToRefPtr<Child1>);
    def("ToMyPtr", &ToMyPtr<Child1>);
    def("ToRefPtr", &ToRefPtr<Child2>);
    def("ToMyPtr", &ToMyPtr<Child2>);
    def("ToRefPtr", &ToRefPtr<GrandChild1>);
    def("ToMyPtr", &ToMyPtr<GrandChild1>);


}

}
